#include "Player.h"
#include "YetiEnemy.h"
#include "RockFall.h"

void Player::update(float frameTime)
{
	// Create instance of input handler.
	const InputHandler* handler = game->getInputHandler();
	// Get a pointer to the main game object.
	RockFall* rockfall = (RockFall*)game;

	/**
	 * Check input handler for keys pressed and perform action
	 * dependant on the testing conditions.
	 */
#pragma region CONTROLS
	// Check that the current state is set to play.
	if (rockfall->stateMachine.getCurrentState() == rockfall->stateMachine.getState("Play"))
	{
		/** Press right shift to throw a rock. */
		if (handler->isKeyDown(DIK_RSHIFT) && !handler->wasKeyDown(DIK_RSHIFT))
		{
			// Set player animation to attack.
			playerAnims->playAnimation("Attack");
			// Throwing rock is not available at all time, check the correct level is loaded.
			if (LevelManager::getCurrentID() == 2)
			{
				// The initial velocity of the rock when thrown.
				D3DXVECTOR3 rockVelocity = D3DXVECTOR3(800, 200, 0);
				// Make sure to throw the rock in the correct direction.
				if (flip == true)
					rockVelocity.x *= -1;
				// Add player velocity to rock velocity.
				rockVelocity.x += particle.velocity.x;
				// Access the rock texture.
				RockFall* rockfall = (RockFall*)game;
				// Set the rock position, takeing into account level offset.
				D3DXVECTOR3 tempPos = position + LevelManager::getCurrentLevel()->getOffset();
				// Create the new rock.
				Rock* rock = new Rock(tempPos, rockVelocity, rockfall->rockTexture, LevelManager::getCurrentLevel());
				// Add it to the list rocks, limits of the number of rock is handled below.
				rocks.push_front(rock);
			}
		}
		/** Press Return to melle attack */
		if (handler->isKeyDown(DIK_RETURN))
		{
			// Set player animation to attack.
			playerAnims->playAnimation("Attack");
			// Stop melle object continuously being created when button is pressed.
			/*if (!handler->wasKeyDown(DIK_RETURN))
			{*/
				// Determine the position of the melle texture depending on flip.
				D3DXVECTOR3 tempPos;
				/*if (flip)*/
					tempPos = position + LevelManager::getCurrentLevel()->getOffset() - D3DXVECTOR3(8, 8, 0);
				/*else
					tempPos = position + LevelManager::getCurrentLevel()->getOffset() + D3DXVECTOR3(96, 0, 0);*/
				// Create the melle object.
				Melle* hit = new Melle(tempPos, rockfall->melleTexture);
				// Add it to the melle list.
				melle.push_front(hit);
				// Start timer, this is used to destroy the object after some time.
				melleTimer = 0.0f;
			//}
		}
		/** Press Space to Jump. */
		else if (handler->isKeyDown(DIK_SPACE) && !handler->wasKeyDown(DIK_SPACE))
		{
			// Set the player animation to jump.
			playerAnims->playAnimation("Jump");
			
			// Implement floor contact test HERE...

			// Apply a force upwards and play animation.
			particle.force = D3DXVECTOR3(0.0f, -17000000.0f, 0.0f);

		}
		/** Press Up or W to Climb up ladder if colliding with ladder. */
		else if ((handler->isKeyDown(DIK_UP) || handler->isKeyDown(DIK_W)) && climb == true)
		{
			// Set player animation to climb.
			playerAnims->playAnimation("Climb");
			// Move the player up the ladder.
			particle.velocity.y = -100.0f;
			// Limit horizontal velocity during climbing.
			particle.velocity.x = particle.velocity.x * 0.5f;
		}
		/** Press Down or S to Climbing down ladder if colliding with ladder. */
		else if ((handler->isKeyDown(DIK_DOWN) || handler->isKeyDown(DIK_S)) && climb == true)
		{
			playerAnims->playAnimation("Climb");
			particle.velocity.y = 200.0f;
			particle.velocity.x = particle.velocity.x * 0.5f;
		}
		/** Press Left or A to move left. */
		else if(handler->isKeyDown(DIK_LEFT) || handler->isKeyDown(DIK_A))
		{
			// Set player animation to run.
			playerAnims->playAnimation("Run");
			// Used to flip the player animation horizontally.
			flip = true;
			// Move the player left.
			particle.velocity.x = -200.0f;
		}
		/** Press Right or D to move left. */
		else if (handler->isKeyDown(DIK_RIGHT) || handler->isKeyDown(DIK_D))
		{
			// Set player animation to run.
			playerAnims->playAnimation("Run");
			// Used to flip the player animation horizontally.
			flip = false;
			// Move the player right.
			particle.velocity.x = 200.0f;
		}
		/** Default animation, when no input is recieved. */
		else 
		{
			// Set player animation to idle.
			playerAnims->playAnimation("Idle");
			// Stop the player from moving.
			particle.velocity = D3DXVECTOR3(0, particle.velocity.y , 0);
		}
	}
	/** Update animations and particle. Every frame. */
	playerAnims->update(frameTime);
	particle.update(frameTime);
#pragma endregion

	/**
	 * Destroy weapon entities in the event too many exist or weapon
	 * lifespan has run out.
	 */
#pragma region WEAPONSCONSTRAINTS
	/** A 10 rocks limit avoids physics particle glitches when dealing
	 * with large numbers of rocks, each colliding with the level every
	 *frame. */
	if (rocks.size() > 10)
	{
		// Remove the rock from the entity manager.
		EntityManager::removeEntity(rocks.back());
		// And then from the list rocks.
		rocks.pop_back();
	}	
	/** Melle attacks differ, only one melle texture is allowed. The same
	 * method is used for simplicity. */
	if (melle.size() > 0)
	{
		// Incriment the melle lifespan timer.
		melleTimer += frameTime;
		// In the event the player presses melle again or timer reaches 0.1secs
		if (melle.size() > 1 || melleTimer >= 0.1f)
		{
			// Remove melle object from entity manager.
			EntityManager::removeEntity(melle.back());
			// And also the melle list.
			melle.pop_back();
		}
	}
#pragma endregion

	/**
	 * Check when player collides with level.
	 */
#pragma region LEVELCONTACT
	/** Calcuate the difference between physics particle position and
	 * player entity position. */
    D3DXVECTOR3 movement = particle.position - position;
	// Obtain current player animation texture dimensions.
	D3DXVECTOR3 size(playerAnims->getCurrentAnimation()->getFrameWidth(),
		playerAnims->getCurrentAnimation()->getFrameHeight(), 0);
	// Create bounding box.
	BoundingBox bounds = { position, size };
	// And check if the player is touching any level tiles.
	position = level->checkCollision(this, bounds, movement);

	/** COMMENT HERE */
	if (D3DXVec3Length(&(position - particle.position)) > 0.1f)
	{
		D3DXVECTOR3 contactNormal = position - particle.position;
		D3DXVec3Normalize(&contactNormal, &contactNormal);
		Particle contactParticle(particle.position, D3DXVECTOR3(0, 0, 0), 1000.0f);
		Particle* particles[2] = { &particle, &contactParticle };
		Contact contact(particles, 1.0f, contactNormal);
		contact.resolve(frameTime);
	}
	particle.position = position;
#pragma endregion

	/**
	 * Check collision with ladder entites.
	 */
#pragma region LADDERCONTACT
	// Collect the list of ladders from entity manager and get an iterator.
	std::list<Entity*>* ladders = EntityManager::getNamedEntities("Ladder");
	std::list<Entity*>::iterator iter = ladders->begin();
	// Initialise a counter of ladder collisions.
	int ladderTouched = 0;
	// Check if player is touching any ladder entities.
	for (; iter != ladders->end(); iter++)
	{
		// Skip the current ladder if it is not displayed.
		if ((*iter)->isAlive() == true)
		{
			// Collect the ladders texture and create a bounding box.
			D3DSURFACE_DESC ladderDesc;
			IDirect3DTexture9* ladderTex = (*iter)->getTexture();
			ladderTex->GetLevelDesc(0, &ladderDesc);
			D3DXVECTOR3 ladderSize = D3DXVECTOR3(ladderDesc.Width, ladderDesc.Height, 0.0f);
			// Take into account the level position / offset.
			D3DXVECTOR3 ladderPos = (*iter)->getPosition() - LevelManager::getCurrentLevel()->getOffset();
			BoundingBox ladderBounds = { ladderPos, ladderSize };
			// Check if the player and ladder textures intersect.
			if (TestCollision(bounds, ladderBounds))
				ladderTouched++; // Add to counter if true.
		}
	}
	/** The counter is used instead of the bool because as each iteration
	 * throught he ladder list fails the collision test the bool would be 
	 * reset to false therefore the player can not climb. */
	if (ladderTouched > 0)
		climb = true; // When a ladder has been touched, player can climb.
	else
		climb = false; // Otherwise player can't climb.
	// Reset counter to ensure next pass will have the proper result.
	ladderTouched = 0;
#pragma endregion

	/**
	 * Check collision with level switcher entities.
	 */
#pragma region ENDLEVEL
	// Get end level texture dimensions and create a bounding box from them.
	D3DSURFACE_DESC endDesc;
	IDirect3DTexture9* endTex = EntityManager::getEntity(1001)->getTexture();
	endTex->GetLevelDesc(0, &endDesc);
	D3DXVECTOR3 endSize = D3DXVECTOR3(endDesc.Width, endDesc.Height, 0.0f);
	BoundingBox endBounds = { EntityManager::getEntity(1001)->getPosition(), endSize };
	
	YetiEnemy* yeti = (YetiEnemy*)EntityManager::getEntity(2000);

	// Check collision with end level entity
	if (TestCollision(bounds, endBounds))
	{
		// Test that next level is present before changing level.
		if (LevelManager::levelExists(LevelManager::getCurrentID() + 1))
		{
			// Change current level.
			LevelManager::setCurrentID(LevelManager::getCurrentID() + 1);
			// Reset start and end level entity positions.
			EntityManager::getEntity(1000)->setPosition(LevelManager::getCurrentLevel()->getStartPoint());
			EntityManager::getEntity(1001)->setPosition(LevelManager::getCurrentLevel()->getEndPoint());
			// Update level player is colliding with.
			setLevel(LevelManager::getCurrentLevel());
			// Reset player position to start position of new level.
			setPosition(LevelManager::getCurrentLevel()->getStartPoint());
			particle.acceleration = D3DXVECTOR3(0, 0, 0);
			particle.velocity = D3DXVECTOR3(0, 0, 0);
			// Reset the level offset for new level.
			rockfall->setLevelPosition(LevelManager::getCurrentLevel()->getInitOffset());
		}
		else if (yeti->getHealth() <= 0)
		{
			// Game winning state.
			rockfall->stateMachine.changeState("Winning");
			rockfall->winScreen->setCurrentSlide(1);
			// Reset to first level so the player can play again.
			LevelManager::setCurrentID(0);
			// Reset start and end level entity positions.
			EntityManager::getEntity(1000)->setPosition(LevelManager::getCurrentLevel()->getStartPoint());
			EntityManager::getEntity(1001)->setPosition(LevelManager::getCurrentLevel()->getEndPoint());
			// Update level player is colliding with.
			setLevel(LevelManager::getCurrentLevel());
			// Reset player position to start position of new level.
			setPosition(LevelManager::getCurrentLevel()->getStartPoint());
			particle.acceleration = D3DXVECTOR3(0, 0, 0);
			particle.velocity = D3DXVECTOR3(0, 0, 0);
			// Reset the level offset for new level.
			rockfall->setLevelPosition(LevelManager::getCurrentLevel()->getInitOffset());
		}
	}
#pragma endregion

	/** When player enters stunned state stunned timer is added to. */
	if (stunned)
		stunnedTimer += frameTime;
}

void Player::draw(ID3DXSprite* sprite)
{
	//First we work out the centre of the frame
	D3DXVECTOR2 centre;
	centre.x = playerAnims->getCurrentAnimation()->getFrameWidth() / 2;
	centre.y = playerAnims->getCurrentAnimation()->getFrameHeight() / 2;

	/* Now we create a transformation matrix. If flipped, we scale X by -1
	 * we also use the position vector in our transaction */
	D3DXMATRIX mat;
	if(flip)
		D3DXMatrixTransformation2D(&mat, &centre, 0,
								   &D3DXVECTOR2(-1, 1), 0, 0,
								   &D3DXVECTOR2(position.x, position.y));
	else 
		D3DXMatrixTransformation2D(&mat, 0, 0, 0, 0, 0,
								   &D3DXVECTOR2(position.x, position.y));

	//Now we use this transformation to draw with, we set the draw position to 0 to avoid issues
	
	playerAnims->draw(sprite, D3DXVECTOR3(0, 0, 0), mat);
}

void Player::handleMessage(Message message)
{
	RockFall* rockfall = (RockFall*)game;
	// When player recieves die message.
	if (message.message == "Hit")
	{
		playerAnims->playAnimation("Die");
		if (stunned == false)
		{
			stunned = true;
		}
		else if (stunnedTimer > 5.0f)
		{
			rockfall->stateMachine.changeState("Winning");
			rockfall->winScreen->setCurrentSlide(2);
			stunnedTimer = 0;
		}
	}
}